/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.document.reflection;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 实体类反射表辅助类,支持JPA注解
 *
 * @FileName BeanMappingHelper.java
 * @Author WangMengmeng E-mail:wangmeng6447@163.com
 * @Version 1.0
 */
public class BeanMappingHelper {

    private static final Logger log = LoggerFactory.getLogger(BeanMappingHelper.class);

    /**
     * 缓存反射类表信息
     */
    private static Map<String, BeanMapping> cache = new ConcurrentHashMap<String, BeanMapping>();

    /**
     * 根据实体类反射获取表信息
     *
     * @param clazz 反射实体类
     * @return
     */
    public synchronized static BeanMapping getTableInfo(Class<?> clazz) {
        BeanMapping ti = cache.get(clazz.getName());
        if (ti != null) {
            return ti;
        }
        List<Field> list = getAllFields(clazz);
        BeanMapping beanMapping = new BeanMapping();

        /* 表注解 */
        Table table = clazz.getAnnotation(Table.class);
        if (table != null && table.name() != null && table.name().trim().length() > 0) {
            beanMapping.setTableName(table.name());
        } else {
            beanMapping.setTableName(camelToUnderline(clazz.getSimpleName()));
        }

        List<BeanFieldMapping> fieldList = new ArrayList<BeanFieldMapping>();
        ConcurrentHashMap<String, String> fieldMap = new ConcurrentHashMap<String, String>();
        for (Field field : list) {

            /* 获取get方法 */
            Method method = null;
            try {
                //获取get方法名称: get + 属性名首字母大写
                char[] ch = field.getName().toCharArray();
                if (ch[0] >= 'a' && ch[0] <= 'z') {
                    ch[0] = (char) (ch[0] - 32);
                }
                String methodName = "get" + new String(ch);
                Class<?>[] classes = new Class[]{};
                //查找方法
                method = clazz.getMethod(methodName, classes);

            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }

            /* 获取主键注解 */
            Id tableId = field.getAnnotation(Id.class);
            if (tableId == null && method != null) {
                tableId = method.getAnnotation(Id.class);
            }
            if (tableId != null) {
                beanMapping.setKeyProperty(field.getName());
            }

            /* 获取字段注解 */
            Column tableColumn = field.getAnnotation(Column.class);
            if (tableColumn == null && method != null) {
                //获取注解
                tableColumn = method.getAnnotation(Column.class);
            }

            /* 获取注解内容 */
            if (tableColumn != null && tableColumn.name() != null && !"".equals(tableColumn.name())) {
                fieldList.add(new BeanFieldMapping(tableColumn.name(), field.getName()));
                fieldMap.put(tableColumn.name().toLowerCase(), field.getName());
                if (tableId != null) {
                    beanMapping.setKeyColumn(tableColumn.name());
                }
                continue;
            }

            /* 字段 */
            fieldList.add(new BeanFieldMapping(field.getName(), field.getName()));
            fieldMap.put(field.getName().toLowerCase(), field.getName());
            beanMapping.setKeyColumn(field.getName());
            continue;

        }

        /* 字段列表 */
        beanMapping.setFieldList(fieldList);
        beanMapping.setFieldMap(fieldMap);
        cache.put(clazz.getName(), beanMapping);
        return beanMapping;
    }

    /**
     * 去掉下划线转换为大写
     */
    private static String camelToUnderline(String param) {
        if (param == null || "".equals(param.trim())) {
            return "";
        }
        int len = param.length();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            char c = param.charAt(i);
            if (Character.isUpperCase(c) && i > 0) {
                sb.append("_");
            }
            sb.append(Character.toUpperCase(c));
        }
        return sb.toString();
    }

    /**
     * 获取该类的所有属性列表
     *
     * @param clazz 反射类
     * @return
     */
    private static List<Field> getAllFields(Class<?> clazz) {
        List<Field> result = new LinkedList<Field>();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {

            /* 过滤 transient 关键字修饰的属性 */
            if (Modifier.isTransient(field.getModifiers())) {
                continue;
            }

            /* 过滤 static 关键字修饰的属性 */
            if (Modifier.isStatic(field.getModifiers())) {
                continue;
            }
            result.add(field);
        }

        /* 处理父类字段 */
        Class<?> superClass = clazz.getSuperclass();
        if (superClass.equals(Object.class)) {
            return result;
        }
        result.addAll(getAllFields(superClass));
        return result;
    }

    /**
     * List-Map 转 List-Bean
     *
     * @param list  List-Map
     * @param clazz Bean.class
     * @return java.util.List<E> 返回值不为null
     */
    public static <E> List<E> transformList(List<Map<String, Object>> list, Class<E> clazz) {
        //声明返回值List
        List<E> transformList = new ArrayList<>();
        //校验数据
        if (list == null || list.isEmpty()) {
            return transformList;
        }
        //获取映射关系
        BeanMapping beanMapping = BeanMappingHelper.getTableInfo(clazz);
        Map<String, String> fieldMap = beanMapping.getFieldMap();
        //遍历转换
        for (Map map : list) {
            //map转bean
            transformList.add(BeanUtil.mapToBean(lowerKey(map), clazz, CopyOptions.create().setFieldMapping(fieldMap)));
        }
        return transformList;
    }

    /**
     * map转bean
     *
     * @param map   数据map
     * @param clazz 要转换的实体Class
     * @return E
     */
    public static <E> E transform(Map<String, Object> map, Class<E> clazz) {

        BeanMapping beanMapping = BeanMappingHelper.getTableInfo(clazz);
        //获取字段映射信息
        Map<String, String> fieldMap = beanMapping.getFieldMap();
        //复制bean
        return BeanUtil.mapToBean(lowerKey(map), clazz, CopyOptions.create().setFieldMapping(fieldMap));
    }

    public static List<Map<String, Object>> lowerKey(List<Map<String, Object>> list) {
        if (list == null) {
            return null;
        }
        List<Map<String, Object>> newList = new ArrayList<>();
        for (Map<String, Object> map : list) {
            newList.add(lowerKey(map));
        }
        return newList;
    }

    public static Map<String, Object> lowerKey(Map<String, Object> map) {
        Set<String> keys = map.keySet();
        Map<String, Object> lowerMap = new HashMap<>();
        for (String key : keys) {
            lowerMap.put(key.toLowerCase(), map.get(key));
        }
        return lowerMap;
    }

}
